package com.sky.controller.user;
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信登录流程：
 * **步骤分析：**
 *
 * 1. 小程序端，调用wx.login()获取code，就是授权码。
 * 2. 小程序端，调用wx.request()发送请求并携带code，请求开发者服务器(自己编写的后端服务)。
 * 3. 开发者服务端，通过HttpClient向微信接口服务发送请求，并携带appId+appsecret+code三个参数。
 * 4. 开发者服务端，接收微信接口服务返回的数据，session_key+opendId等。opendId是微信用户的唯一标识。
 * 5. 开发者服务端，自定义登录态，生成令牌(token)和openid等数据返回给小程序端，方便后绪请求身份校验。
 * 6. 小程序端，收到自定义登录态，存储storage。
 * 7. 小程序端，后绪通过wx.request()发起业务请求时，携带token。
 * 8. 开发者服务端，收到请求后，通过携带的token，解析当前登录用户的id。
 * 9. 开发者服务端，身份校验通过后，继续相关的业务逻辑处理，最终返回业务数据。
 *
 * 说明：
 * 1. 调用 [wx.login()](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html) 获取临时登录凭证code，并回传到开发者服务器。(点击确定按钮，获取授权码，每个授权码只能使用一次，每次测试，需重新获取)
 * 2. 调用 [auth.code2Session](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html) 接口，
 * 换取用户唯一标识 OpenID、 用户在微信开放平台帐号下的唯一标识UnionID若当前小程序已绑定到微信开放平台帐号）和会话密钥 session_key。
 * 之后开发者服务器可以根据用户标识来生成自定义登录态，用于后续业务逻辑中前后端交互时识别用户身份。
 */



/**
 * CachePut：将方法返回值放入缓存
 * value：缓存的名称，每个缓存名称下面可以有多个key
 * key：缓存的key ----------> 支持Spring的表达式语言SPEL语法
 *
 * 当前UserController的save方法是用来保存用户信息的，我们希望在该用户信息保存到数据库的同时，
 * 也往缓存中缓存一份数据，我们可以在save方法上加上注解 @CachePut，用法如下：
 * 说明：key的写法如下
 * #user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;
 * #result.id : #result代表方法返回值，该表达式 代表以返回对象的id属性作为key ；
 * #p0.id：#p0指的是方法中的第一个参数，id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
 * #a0.id：#a0指的是方法中的第一个参数，id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
 * #root.args[0].id:#root.args[0]指的是方法中的第一个参数，id指的是第一个参数的id属性,也就是使用第一个参数
 * 的id属性作为key
 */
/**
 * @CacheEvict 说明：
 *	作用: 清理指定缓存
 *value: 缓存的名称，每个缓存名称下面可以有多个key
 *key: 缓存的key  ----------> 支持Spring的表达式语言SPEL语法
 *   @CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据
 *   @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
 */
/**
 *@Cacheable 说明:
 *作用: 在方法执行前，spring先查看缓存中是否有数据，如果有数据，则直接返回缓存数据；若没有数据，调用方法并将方法返回值放到缓存中
 *value: 缓存的名称，每个缓存名称下面可以有多个key
 *key: 缓存的key  ----------> 支持Spring的表达式语言SPEL语法
 */

/**
 * @EnableCaching 开启缓存注解功能，通常加在启动类上
 * @Cacheable   在方法执行前先查询缓存中是否有数据，如果有数据，则直接返回缓存数据；如果没有缓存数据，调用方法并将方法返回值放到缓存中
 * @CachePut   将方法的返回值放到缓存中
 * @CacheEvict   将一条或多条数据从缓存中删除
 *
 * 实现思路,实现步骤：
 *1). 导入Spring Cache和Redis相关maven坐标
 *2). 在启动类上加入@EnableCaching注解，开启缓存注解功能
 *3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解
 *4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解
 *
 * Spring Cache 是一个框架，实现了基于注解的缓存功能，只需要简单地加一个注解，就能实现缓存功能。
 * Spring Cache 提供了一层抽象，底层可以切换不同的缓存实现，例如：
 * - EHCache
 * - Caffeine
 * - Redis(常用)
 */

@RestController
@RequestMapping("/user/user")   // 请求路径: /user/user/login,第一个user代表用户端，第二个user代表用户模块。
@Api(tags = "C端用户相关接口")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private JwtProperties jwtProperties;


    /**
     * 微信登录
     * @param userLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation("微信登录")
    //code通过请求体以json的方式提交过来
    //返回数据有code和data，data包括id（当前用户在数据库的主键值），openid（微信openid），token（jwt令牌）
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录:{}",userLoginDTO.getCode());

        //微信登录
        User user = userService.wxLogin(userLoginDTO);

        //为微信用户生成jwt令牌
        Map<String,Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID,user.getId());     //用户的唯一标识，即用户的主键值
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims);

        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);
        /**
         * 登录成功之后，已经响应回来token，然后接下来每一次业务请求的时候，都会在请求头里面把token带过去
         * 请求头叫authentication:，在微信开发者工具的调试器里的network，例如list业务请求的请求头里
         *
         *   问题：token被带过来，需要进行校验，所以需要写一个拦截器
         *来校验用户端也就是小程序这一端发送过来的请求，来校验token是否合法，因为小程序这一端也要保证当前用户是一个登录的状态，否则也是无法使用的
         */
    }
}
